---
title: Flows
description: Group multiple steps to be visible in diagrams in Workbench
---

Flows group related Steps together so you can see them as connected workflows in Workbench. Add `flows` to your Step config - it's an array of flow names.

## How It Works

Add `flows` to any Step config. Each string is a flow name.

<Tabs items={['TypeScript', 'Python', 'JavaScript']}>
<Tab value='TypeScript'>

```typescript
export const config: ApiRouteConfig = {
  name: 'CreateResource',
  type: 'api',
  path: '/resources',
  method: 'POST',
  flows: ['resource-management']
}
```

</Tab>
<Tab value='Python'>

```python
config = {
    "name": "CreateResource",
    "type": "api",
    "path": "/resources",
    "method": "POST",
    "flows": ["resource-management"]
}
```

</Tab>
<Tab value='JavaScript'>

```javascript
const config = {
  name: 'CreateResource',
  type: 'api',
  path: '/resources',
  method: 'POST',
  flows: ['resource-management']
}
```

</Tab>
</Tabs>

---

## Example

Two Steps working together in one flow.

![API and Event Steps connected in a flow](../img/flows-api-event.png)

**API Step - Create resource:**

<Tabs items={['TypeScript', 'Python', 'JavaScript']}>
<Tab value='TypeScript'>

```typescript title="steps/create-resource.step.ts"
import { ApiRouteConfig, Handlers } from 'motia'

export const config: ApiRouteConfig = {
  name: 'CreateResource',
  type: 'api',
  path: '/resources',
  method: 'POST',
  flows: ['resource-management'],
  emits: ['send-email']
}

export const handler: Handlers['CreateResource'] = async (req, { emit, logger }) => {
  logger.info('Creating resource', { title: req.body.title })
  
  await emit({ 
    topic: 'send-email', 
    data: { email: req.body.email } 
  })
  
  return { status: 201, body: { id: '123' } }
}
```

</Tab>
<Tab value='Python'>

```python title="steps/create_resource_step.py"
config = {
    "name": "CreateResource",
    "type": "api",
    "path": "/resources",
    "method": "POST",
    "flows": ["resource-management"],
    "emits": ["send-email"]
}

async def handler(req, ctx):
    ctx.logger.info("Creating resource", {"title": req["body"]["title"]})
    
    await ctx.emit({
        "topic": "send-email",
        "data": {"email": req["body"]["email"]}
    })
    
    return {"status": 201, "body": {"id": "123"}}
```

</Tab>
<Tab value='JavaScript'>

```javascript title="steps/create-resource.step.js"
const config = {
  name: 'CreateResource',
  type: 'api',
  path: '/resources',
  method: 'POST',
  flows: ['resource-management'],
  emits: ['send-email']
}

const handler = async (req, { emit, logger }) => {
  logger.info('Creating resource', { title: req.body.title })
  
  await emit({ 
    topic: 'send-email', 
    data: { email: req.body.email } 
  })
  
  return { status: 201, body: { id: '123' } }
}

module.exports = { config, handler }
```

</Tab>
</Tabs>

**Event Step - Send email:**

<Tabs items={['TypeScript', 'Python', 'JavaScript']}>
<Tab value='TypeScript'>

```typescript title="steps/send-email.step.ts"
import { EventConfig, Handlers } from 'motia'

export const config: EventConfig = {
  name: 'SendEmail',
  type: 'event',
  subscribes: ['send-email'],
  flows: ['resource-management']
}

export const handler: Handlers['SendEmail'] = async (input, { logger }) => {
  logger.info('Sending email', { email: input.email })
  // Email sending logic here
}
```

</Tab>
<Tab value='Python'>

```python title="steps/send_email_step.py"
config = {
    "name": "SendEmail",
    "type": "event",
    "subscribes": ["send-email"],
    "flows": ["resource-management"]
}

async def handler(input, ctx):
    ctx.logger.info("Sending email", {"email": input["email"]})
    # Email sending logic here
```

</Tab>
<Tab value='JavaScript'>

```javascript title="steps/send-email.step.js"
const config = {
  name: 'SendEmail',
  type: 'event',
  subscribes: ['send-email'],
  flows: ['resource-management']
}

const handler = async (input, { logger }) => {
  logger.info('Sending email', { email: input.email })
  // Email sending logic here
}

module.exports = { config, handler }
```

</Tab>
</Tabs>

👉 Both Steps have `flows: ['resource-management']`. In Workbench, they appear connected.

---

## Multiple Flows

A Step can belong to multiple flows.

<Tabs items={['TypeScript', 'Python', 'JavaScript']}>
<Tab value='TypeScript'>

```typescript
export const config: EventConfig = {
  name: 'SendEmail',
  type: 'event',
  subscribes: ['send-email'],
  flows: ['resource-management', 'user-onboarding']
}
```

</Tab>
<Tab value='Python'>

```python
config = {
    "name": "SendEmail",
    "type": "event",
    "subscribes": ["send-email"],
    "flows": ["resource-management", "user-onboarding"]
}
```

</Tab>
<Tab value='JavaScript'>

```javascript
const config = {
  name: 'SendEmail',
  type: 'event',
  subscribes: ['send-email'],
  flows: ['resource-management', 'user-onboarding']
}
```

</Tab>
</Tabs>

👉 This Step appears in both flows in Workbench.

## Steps Without Flows

Steps work fine without flows.

<Tabs items={['TypeScript', 'Python', 'JavaScript']}>
<Tab value='TypeScript'>

```typescript
export const config: ApiRouteConfig = {
  name: 'HealthCheck',
  type: 'api',
  path: '/health',
  method: 'GET'
}
```

</Tab>
<Tab value='Python'>

```python
config = {
    "name": "HealthCheck",
    "type": "api",
    "path": "/health",
    "method": "GET"
}
```

</Tab>
<Tab value='JavaScript'>

```javascript
const config = {
  name: 'HealthCheck',
  type: 'api',
  path: '/health',
  method: 'GET'
}
```

</Tab>
</Tabs>

---

## Flows in Workbench

Workbench has a dropdown to filter by flow. Select a flow to see only the Steps that belong to it.

![Flow dropdown in Workbench](../img/drop-down-flow.png)

### Virtual Connections

Use `virtualEmits` and `virtualSubscribes` for visualization without actual events:

<Tabs items={['TypeScript', 'Python', 'JavaScript']}>
<Tab value='TypeScript'>

```typescript
export const config: ApiRouteConfig = {
  name: 'CreateResource',
  type: 'api',
  path: '/resources',
  method: 'POST',
  virtualEmits: ['approval.required'],
  flows: ['resource-management']
}
```

</Tab>
<Tab value='Python'>

```python
config = {
    "name": "CreateResource",
    "type": "api",
    "path": "/resources",
    "method": "POST",
    "virtualEmits": ["approval.required"],
    "flows": ["resource-management"]
}
```

</Tab>
<Tab value='JavaScript'>

```javascript
const config = {
  name: 'CreateResource',
  type: 'api',
  path: '/resources',
  method: 'POST',
  virtualEmits: ['approval.required'],
  flows: ['resource-management']
}
```

</Tab>
</Tabs>

Virtual connections show as gray/dashed lines in Workbench. Real connections (from `emits` and `subscribes`) show as dark solid lines.

![Virtual connections with labels in Workbench](../img/virtual-emit-subscribe.png)

### Labels

Add labels to connections in Workbench:

<Tabs items={['TypeScript', 'Python', 'JavaScript']}>
<Tab value='TypeScript'>

```typescript
export const config: ApiRouteConfig = {
  name: 'SendEmail',
  type: 'api',
  path: '/send',
  method: 'POST',
  virtualEmits: [
    { topic: 'email-sent', label: 'Email delivered' },
    { topic: 'email-failed', label: 'Failed to send', conditional: true },
  ],
  flows: ['notifications']
}
```

</Tab>
<Tab value='Python'>

```python
config = {
    "name": "SendEmail",
    "type": "api",
    "path": "/send",
    "method": "POST",
    "virtualEmits": [
        {"topic": "email-sent", "label": "Email delivered"},
        {"topic": "email-failed", "label": "Failed to send", "conditional": True}
    ],
    "flows": ["notifications"]
}
```

</Tab>
<Tab value='JavaScript'>

```javascript
const config = {
  name: 'SendEmail',
  type: 'api',
  path: '/send',
  method: 'POST',
  virtualEmits: [
    { topic: 'email-sent', label: 'Email delivered' },
    { topic: 'email-failed', label: 'Failed to send', conditional: true }
  ],
  flows: ['notifications']
}
```

</Tab>
</Tabs>

### NOOP Steps

NOOP Steps don't run code. They're for visualization only:

<Tabs items={['TypeScript', 'Python', 'JavaScript']}>
<Tab value='TypeScript'>

```typescript
import { NoopConfig } from 'motia'

export const config: NoopConfig = {
  type: 'noop',
  name: 'ApprovalGate',
  virtualEmits: ['approved'],
  virtualSubscribes: ['approval.required'],
  flows: ['resource-management']
}

// No handler needed
```

</Tab>
<Tab value='Python'>

```python
config = {
    "type": "noop",
    "name": "ApprovalGate",
    "virtualEmits": ["approved"],
    "virtualSubscribes": ["approval.required"],
    "flows": ["resource-management"]
}

# No handler needed
```

</Tab>
<Tab value='JavaScript'>

```javascript
const config = {
  type: 'noop',
  name: 'ApprovalGate',
  virtualEmits: ['approved'],
  virtualSubscribes: ['approval.required'],
  flows: ['resource-management']
}

// No handler needed

module.exports = { config }
```

</Tab>
</Tabs>

👉 [Learn about customizing how flows look →](/docs/development-guide/customizing-flows)

---
